﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using Dorm.Helpers;
using Dorm.Maps;

namespace Dorm.Services
{
    public class LoadService
    {
        public List<MappedProperty> EntityMaps { get; set; }

        private DataAccess dataAccess;

        public LoadService(DataAccess _dataAccess)
        {
            dataAccess = _dataAccess;
        }        

        public IList Load(string propertyToLoadName, IList entitiesToFill)
        {
            if (entitiesToFill.Count == 0)
                return entitiesToFill;

            Type entityType = entitiesToFill[0].GetType();

            if (EntityMaps == null)
                EntityMaps = MapLocator.GetMap(entityType);
            
            var otherListMap = EntityMaps.FirstOrDefault(m => m.PropertyName == propertyToLoadName);

            Type otherSingleType = otherListMap.MappingType == MappedProperty.MappingTypes.Reference ?
                                    entityType.GetProperty(propertyToLoadName).PropertyType :
                                    entityType.GetProperty(propertyToLoadName).PropertyType.GetGenericArguments()[0];

            string othertypeName = otherSingleType.Name;

            foreach (var entityToFill in entitiesToFill)
            {
                string childQuery = string.Empty;
                string entityID = entityType.GetProperty("ID").GetValue(entityToFill, null).ToString();

                if (otherListMap.MappingType == MappedProperty.MappingTypes.Reference)
                {
                    string otherIdQuery = "SELECT " + othertypeName + "ID FROM " + entityType.Name + " WHERE ID = " + entityID;
                    childQuery = "SELECT " + othertypeName + ".* FROM " + othertypeName + " WHERE ID = (" + otherIdQuery + ")";
                }
                else if (otherListMap.MappingType == MappedProperty.MappingTypes.HasMany)
                {
                    var otherMap = MapLocator.GetMap(otherSingleType);
                    var parentReferenceProperty = otherMap.FirstOrDefault(m => m.PropertyType == entityType);
                    string foreignPrefix = parentReferenceProperty.PropertyName;
                    
                    childQuery = "SELECT " + othertypeName + ".* FROM " + othertypeName + " WHERE " + foreignPrefix + "ID = " + entityID;
                }
                else if (otherListMap.MappingType == MappedProperty.MappingTypes.HasManyToMany)
                {
                    string manyToManyTableName = MappedProperty.GetManyToManyTableName(entityType.Name, othertypeName);
                    childQuery = "SELECT " + othertypeName + ".* FROM " + manyToManyTableName
                               + " INNER JOIN " + othertypeName + " ON " + othertypeName + ".ID = " + manyToManyTableName + "." + othertypeName + "ID"
                               + " WHERE " + manyToManyTableName + "." + entityType.Name + "ID = " + entityID;
                }

                Type listType = (otherListMap.MappingType == MappedProperty.MappingTypes.Reference) ?
                                typeof(List<>).MakeGenericType(entityType.GetProperty(propertyToLoadName).PropertyType) :
                                entityType.GetProperty(propertyToLoadName).PropertyType;

                if (listType.IsInterface)
                    throw new Exception("Property " + otherListMap.PropertyName +
                                        " should be of type List<T> (cannot be an interface).");
                IList otherEntityList = (IList)Activator.CreateInstance(listType);

                otherEntityList = new DbToEntityMapper()
                    .Map(otherSingleType, otherEntityList, dataAccess.ExecuteReader(childQuery));

                var valueToSet = (otherListMap.MappingType == MappedProperty.MappingTypes.Reference) ?
                                 (otherEntityList as IList)[0]: 
                                  otherEntityList;

                entityType.GetProperty(propertyToLoadName).SetValue(entityToFill, valueToSet, null);
                
            }

            return entitiesToFill;
        }

        internal void LoadAll(IList entitiesToFill)
        {
            Type entityType = entitiesToFill[0].GetType();

            if (EntityMaps == null)
                EntityMaps = MapLocator.GetMap(entityType);

            foreach (var mappedProperty in EntityMaps)
            {
                if (mappedProperty.MappingType == MappedProperty.MappingTypes.Reference
                 || mappedProperty.MappingType == MappedProperty.MappingTypes.HasMany
                 || mappedProperty.MappingType == MappedProperty.MappingTypes.HasManyToMany)
                    Load(mappedProperty.PropertyName, entitiesToFill);
            }
        }
    }
}
